/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "nweb_resource_handler.h"

#include <sys/stat.h>
#include <unistd.h>

#include "base/logging.h"
#include "base/command_line.h"
#include "content/public/common/content_switches.h"
#include "ohos_adapter_helper.h"

namespace OHOS::NWeb {

class NWebResourceReadyCallbackImpl : public NWebResourceReadyCallback {
public:
    NWebResourceReadyCallbackImpl(CefRefPtr<CefCallback> callback) : callback_(callback) {}
    ~NWebResourceReadyCallbackImpl() = default;
    void Continue() override
    {
        LOG(INFO) << "intercept NWebResourceReadyCallbackImpl::Continue";
        callback_->Continue();
    }
  
    void Cancel() override
    {
        LOG(INFO) << "intercept NWebResourceReadyCallbackImpl::Cancel";
        callback_->Cancel();
    }
private:
    CefRefPtr<CefCallback> callback_;
};

void ConvertMapToHeaderMap(
    CefRequest::HeaderMap& headers,
    const std::map<std::string, std::string> response_map) {
  for (auto iter = response_map.begin(); iter != response_map.end(); iter++) {
    const std::string& key = iter->first;
    const std::string& value = iter->second;
    headers.emplace(key, value);
  }
}

NWebResourceHandler::NWebResourceHandler(
    std::shared_ptr<NWebUrlResourceResponse> web_response,
    std::string& str)
    : data_(str) {
  response_ = web_response;
  offset_ = 0;
}

/* CefResourceHandler method begin */
bool NWebResourceHandler::Open(CefRefPtr<CefRequest> request,
                               bool& handle_request,
                               CefRefPtr<CefCallback> callback) {
  LOG(INFO) << "intercept NWebResourceHandler::Open";
  if (response_ == nullptr) {
    return false;
  }
  if (response_->ResponseDataStatus()) {
    LOG(INFO) << "intercept open reponse sync";
    handle_request = true;
    data_ = response_->ResponseData();
    return true;
  }
  LOG(INFO) << "intercept open async";
  handle_request = false;
  std::shared_ptr<NWebResourceReadyCallbackImpl> nwebCb = std::make_shared<NWebResourceReadyCallbackImpl>(callback);
  response_->PutResponseReadyCallback(nwebCb);
  return true;
}

bool NWebResourceHandler::ReadStringData(void* data_out, int bytes_to_read, int& bytes_read) {
  LOG(INFO) << "intercept ReadStringData";
  bool has_data = false;
  bytes_read = 0;

  data_ = response_->ResponseData();
  if (offset_ < data_.length()) {
    // Copy the next block of data into the buffer.
    int transfer_size =
        std::min(bytes_to_read, static_cast<int>(data_.length() - offset_));
    memcpy(data_out, data_.c_str() + offset_, transfer_size);
    offset_ += transfer_size;

    bytes_read = transfer_size;
    has_data = true;
  }

  return has_data;
}

int64_t NWebResourceHandler::GetFileSizeByFd() {
  if (resource_data_len_ == 0) {
    int fd = response_->ResponseFileHandle();
    if (fd <= 0) {
      LOG(ERROR) << "intercept get fd invalid : " << fd;
      return -1;
    }
    struct stat st;
    if (fstat(fd, &st) == -1) {
      return -1;
    }
    resource_data_len_ = st.st_size;
  }
  return resource_data_len_;
}

bool NWebResourceHandler::ReadFileData(void* data_out, int bytes_to_read, int& bytes_read) {
  LOG(INFO) << "intercept ReadFileData";
  int fd = response_->ResponseFileHandle();
  if (fd <= 0) {
    bytes_read = fd;
    LOG(ERROR) << "intercept get fd invalid : " << fd;
    return false;
  }
  int ret = read(fd, data_out, bytes_to_read);
  // read fail
  if (ret < 0) {
    LOG(ERROR) << "intercept read fail : " << ret;
    bytes_read = ret;
    return false;
  }
  // completed read
  if (ret == 0) {
    LOG(INFO) << "intercept ReadFileData completed";
    bytes_read = 0;
    return false;
  }
  LOG(INFO) << "intercept contiunue to read:" << ret;
  bytes_read = ret;
  resource_data_offset_ += bytes_read;
  return true;
}

bool NWebResourceHandler::ReadResourceDataByHap() {
  if (resource_data_len_ == 0) {
    std::string resourceUrlHead("resource:/RAWFILE");
    std::string resourceUrl = response_->ResponseResourceUrl();
    if (resourceUrl.find(resourceUrlHead) == std::string::npos) {
      LOG(ERROR) << "intercept find resource head fail : " << resourceUrl;
      return false;
    }
    resourceUrl.erase(0, resourceUrlHead.length());
    std::string resourcePath = "resources/rawfile" + resourceUrl;
    LOG(INFO) << "intercept Read Resource path : " << resourcePath;
    std::string hapPath = base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(switches::kOhosHapPath);
    auto resourceInstance = OHOS::NWeb::OhosAdapterHelper::GetInstance().GetResourceAdapter(hapPath);
    if (!resourceInstance->GetRawFileData(resourcePath, resource_data_len_, resource_data_, false)) {
      LOG(ERROR) << "intercept Read Resource path fail : " << resourcePath;
      return false;
    }
  }
  return true;
}

bool NWebResourceHandler::ReadResourceData(void* data_out, int bytes_to_read, int& bytes_read) {
  bool has_data = false;
  if (ReadResourceDataByHap() == false) {
    return has_data;
  }
  unsigned char* dataPtr = reinterpret_cast<unsigned char*>(resource_data_.get());
  if (resource_data_offset_ < resource_data_len_) {
    int transfer_size = std::min(bytes_to_read, static_cast<int>(resource_data_len_ - resource_data_offset_));
    memcpy(data_out, dataPtr + resource_data_offset_, transfer_size);
    resource_data_offset_ += transfer_size;
    bytes_read = transfer_size;
    has_data = true;
  }
  return has_data;
}

bool NWebResourceHandler::Read(void* data_out,
                               int bytes_to_read,
                               int& bytes_read,
                               CefRefPtr<CefResourceReadCallback> callback) {
  if (response_ == nullptr) {
    bytes_read = -1;
    return false;
  }
  switch (response_->ResponseDataType()) {
    case NWebResponseDataType::NWEB_RESOURCE_URL_TYPE:
      return ReadResourceData(data_out, bytes_to_read, bytes_read);
    case NWebResponseDataType::NWEB_FILE_TYPE:
      return ReadFileData(data_out, bytes_to_read, bytes_read);
    case NWebResponseDataType::NWEB_STRING_TYPE:
      return ReadStringData(data_out, bytes_to_read, bytes_read);
    default:
      break;
  }
  return false;
}

bool NWebResourceHandler::Skip(int64 bytes_to_skip,
                               int64& bytes_skipped,
                               CefRefPtr<CefResourceSkipCallback> callback) {
  if (response_ == nullptr) {
    return CefResourceHandler::Skip(bytes_to_skip, bytes_skipped, callback);
  }
  if (response_->ResponseDataType() == NWebResponseDataType::NWEB_RESOURCE_URL_TYPE) {
    resource_data_offset_ += bytes_to_skip;
    bytes_skipped = resource_data_offset_;
  } else if (response_->ResponseDataType() == NWebResponseDataType::NWEB_FILE_TYPE) {
    int fd = response_->ResponseFileHandle();
    if (fd <= 0 || lseek(fd, bytes_to_skip, SEEK_CUR) == -1) {
      LOG(ERROR) << "intercept Skip get fd invalid : " << fd;
      return CefResourceHandler::Skip(bytes_to_skip, bytes_skipped, callback);
    }
    resource_data_offset_ += bytes_to_skip;
    bytes_skipped = resource_data_offset_;
  } else {
    return CefResourceHandler::Skip(bytes_to_skip, bytes_skipped, callback);
  }
  
  LOG(DEBUG) << "intercept Skip bytes_to_skip: " << bytes_to_skip
             << ", resource_data_offset: " << resource_data_offset_;
  return true;
}

void NWebResourceHandler::GetResponseHeaders(CefRefPtr<CefResponse> response,
                                             int64& response_length,
                                             CefString& redirectUrl) {
  LOG(INFO) << "intercept NWebResourceHandler::GetResponseHeaders";
  if (response_ && response) {
    response->SetMimeType(response_->ResponseMimeType());
    response->SetStatus(response_->ResponseStatusCode());
    response->SetStatusText(response_->ResponseStatus());
    response->SetCharset(response_->ResponseEncoding());
    CefRequest::HeaderMap cef_request_headers;
    std::map<std::string, std::string> request_headers =
        response_->ResponseHeaders();
    ConvertMapToHeaderMap(cef_request_headers, request_headers);
    response->SetHeaderMap(cef_request_headers);
  }
  if (response_->ResponseDataType() == NWebResponseDataType::NWEB_STRING_TYPE) {
    response_length = data_.length();
  } else if (response_->ResponseDataType() == NWebResponseDataType::NWEB_RESOURCE_URL_TYPE) {
    if (ReadResourceDataByHap() == false) {
      response_length = -1;
      return;
    }
    response_length = resource_data_len_ - resource_data_offset_;
  } else {
    if (GetFileSizeByFd() == -1) {
      response_length = -1;
      return;
    }
    response_length = resource_data_len_ - resource_data_offset_;
  }
}

void NWebResourceHandler::Cancel() {
  LOG(INFO) << "intercept NWebResourceHandler::Cancel";
  if (response_ == nullptr) {
    return;
  }
  resource_data_offset_ = 0;
  resource_data_len_ = 0;
  int fd = response_->ResponseFileHandle();
  if (fd <= 0) {
    return;
  }
  response_->PutResponseFileHandle(-1);
  close(fd);
}
/* CefResourceHandler method end */
}  // namespace OHOS::NWeb
